package optimizers.ganeat.jankenpon;

import fitness.Fitness;
import fitness.FitnessResult;
import fitness.JankenponResult;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;


import optimizers.commons.RunConfig;

import optimizers.ganeat.Connection;
import optimizers.ganeat.Constants;
import optimizers.ganeat.novisual.GA;
import optimizers.ganeat.Individual;
import optimizers.ganeat.Node;
import optimizers.ganeat.Species;
import optimizers.ganeat.report.LoggerNEAT;

import visualization.GraphDisplayer;

public class JankenponGA extends GA {

	protected transient ArrayList<JankenponIndividual> ghostOfChristmasPast = new ArrayList<JankenponIndividual>() ;
	
	public JankenponGA(RunConfig config) {
		super(config);
	}

	@Override
	public Individual getInitialIndividual() {
		JankenponIndividual indiv = new JankenponIndividual( innovations, nodeNames, -1, 0, config);
		//Note: those sequences are shared
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.JUDGESTRING, 1, 10, Node.SIMPLE_SEQUENCE)); // A
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.JANSTRING, 25, 10, Node.SIMPLE_SEQUENCE)); // a
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.KENSTRING, 25, 10, Node.SIMPLE_SEQUENCE)); // b
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.PONSTRING,25, 10, Node.SIMPLE_SEQUENCE)); // c
		//For the opponent
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.JANSTRING, 25, 10, Node.SIMPLE_SEQUENCE)); // a
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.KENSTRING, 25, 10, Node.SIMPLE_SEQUENCE)); // b
		indiv.nodeGenes.add(new Node(ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.PONSTRING,25, 10, Node.SIMPLE_SEQUENCE)); // c
		indiv.addConnection(indiv.getNode(ConstantsJankenpon.JANSTRING), indiv.getNode(ConstantsJankenpon.JANSTRING), true, 5); //always play stone
		//indiv.addConnection(indiv.getNode(ConstantsJankenpon.KENSTRING), indiv.getNode(ConstantsJankenpon.KENSTRING), true, 5); //always play paper
		//indiv.addConnection(indiv.getNode(ConstantsJankenpon.PONSTRING), indiv.getNode(ConstantsJankenpon.PONSTRING), true, 5); //always play cissors
		return indiv;
	}

	@Override
	public Individual getNewIndividual(Map<String, Integer> innovations2,
			Map<String, String> nodeNames2, int i, int j) {
		// TODO Auto-generated method stub
		
		return new JankenponIndividual(innovations2, nodeNames2, i, j, config);
	}

	@Override
	public String getProblemName() {
		
		return "Jankenpon";
	}

//	@Override
//	protected void launchEvaluation(Individual[] nextGen, LoggerNEAT logger){
//		//Set the competition
//		Iterator<Individual> it;
//		//To save some of the guys who are good, but were unlucky on the first turn
//		ArrayList<Individual> rattrapage = new ArrayList<Individual>();
//		boolean firstround = true;
//		//for the round's winners
//		ArrayList<Individual> winners = new ArrayList<Individual>(Arrays.asList(nextGen));
//		int numberOfMatches = 0;
//		while(winners.size()>1){
//			it = winners.iterator();
//			int taskCount = 0;
//			while(it.hasNext()){
//				ArrayList<Future<Object>> futures = new ArrayList<Future<Object>>();
//				JankenponIndividual indiv1, indiv2;
//				indiv1 = (JankenponIndividual) it.next();
//				indiv2 = it.hasNext() ? (JankenponIndividual) it.next() : null;
//					futures.add(cservice
//							.submit(new JankenponMatch(indiv1,indiv2)));
//					taskCount++;
//			}
//			
//			winners.removeAll(winners);
//			for (int i = 0; i < taskCount; i++) {
//				try {
//					//System.out.println(this.maxRunTime);
//					Future<Object> future = cservice.poll(this.maxRunTime, TimeUnit.SECONDS);
//					if (future != null) {
//						JankenponMatchResult result = (JankenponMatchResult) future
//								.get();
//						winners.add(result.winner);
//						if(firstround && result.loser != null){
//							rattrapage.add(result.loser);
//						}
//						if(result.winner == null){
//							System.err.println("Possible future interupt");
//						}
//						int index = Arrays.asList(nextGen).indexOf(result.winner);
//						logger.logToMemory(numberOfMatches + ":Indiv " + index
//								+ "(Fitness "
//								+ nextGen[index].getFitnessString()
//								+ ")"
//								+ nextGen[index].printSimpleModel()
//								+ " against "
//								+ (result.loser==null?"nobody":result.loser.printSimpleModel()));
//						numberOfMatches++;
//					} else {
//						//for (Future<Object> future1 : futures) {
//						//	if (future1 != null) {
//						//		future1.cancel(true);
//						//	}
//						//}
//						//logger.logToMemory(i + ": Drop");
//						//break;
//					}
//				} catch (Exception e) {
//					e.printStackTrace();
//				}
//			}
//			firstround = false;
//		}
//	}
	
	@Override
	protected void launchEvaluation(Individual[] nextGen, LoggerNEAT logger){ //Now playing elo
		//Set the competition
		Iterator<Individual> iit;

		
		int numberOfMatches = 0;
	
			iit = Arrays.asList(nextGen).iterator();
			int taskCount = 0;
			ArrayList<Future<Object>> futures = new ArrayList<Future<Object>>();
			
			JankenponIndividual rock = (JankenponIndividual) this.getInitialIndividual();
			JankenponIndividual paper = (JankenponIndividual) this.getInitialIndividual();
			paper.connectionGenes = new ArrayList<Connection>();
			paper.addConnection(paper.getNode(ConstantsJankenpon.KENSTRING), paper.getNode(ConstantsJankenpon.KENSTRING), true, 5);
			JankenponIndividual cissors = (JankenponIndividual) this.getInitialIndividual();
			cissors.connectionGenes = new ArrayList<Connection>();
			cissors.addConnection(cissors.getNode(ConstantsJankenpon.PONSTRING), cissors.getNode(ConstantsJankenpon.PONSTRING), true, 5);
			
			ArrayList<JankenponIndividual> worthyOpponents = new ArrayList<JankenponIndividual>();
			
			//fight the three old wisemen
			while(iit.hasNext()){
				JankenponIndividual indiv1;
				indiv1 = (JankenponIndividual) iit.next();
				futures.add(cservice
						.submit(new JankenponMatch(indiv1,rock)));
				taskCount++;
				futures.add(cservice
						.submit(new JankenponMatch(indiv1,paper)));
				taskCount++;
				futures.add(cservice
						.submit(new JankenponMatch(indiv1,cissors)));
				taskCount++;
				
			}
			
			for (int i = 0; i < taskCount; i++) {
				try {
					//System.out.println(this.maxRunTime);
					Future<Object> future = cservice.poll(this.maxRunTime, TimeUnit.SECONDS);
					if (future != null) {
						JankenponMatchResult result = (JankenponMatchResult) future
								.get();
						
						if(result.winner == null){
							System.err.println("Possible future interupt");
						}
						JankenponMatch.updatePlayers(result);
					} else {
						//for (Future<Object> future1 : futures) {
						//	if (future1 != null) {
						//		future1.cancel(true);
						//	}
						//}
						//logger.logToMemory(i + ": Drop");
						//break;
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
			for(Individual indiv : nextGen){
			if((indiv.getFitness() > 2.5*ConstantsJankenpon.rounds - 15) || JankenponIndividual.debug){
				if(!worthyOpponents.contains(indiv)){
					worthyOpponents.add((JankenponIndividual) indiv);
					logger.logToMemory("Here comes a new challenger: "+indiv.individualIndex+" "+indiv.printSimpleModel());
				}
				
			}
			}
			
			Iterator<JankenponIndividual> it = worthyOpponents.iterator();
			taskCount = 0;
//			ArrayList<JankenponIndividual> materializedGhosts = new ArrayList<JankenponIndividual>();
//			double scoremodification = 0;
//			for(int i=0;i<this.ghostOfChristmasPast.size();i++){
//				if(this.ghostOfChristmasPast.get(i).fitnessResult.finalResult() > JankenponMatch.bossRatio){
//					scoremodification += this.ghostOfChristmasPast.get(i).fitnessResult.finalResult();
//				}
//				JankenponIndividual ghost = this.ghostOfChristmasPast.get(i);
//				
//				try {
//					materializedGhosts.add((JankenponIndividual) this.clone(ghost));
//				} catch (CloneNotSupportedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				
//				}
//			}
			while(it.hasNext()){
				
				JankenponIndividual indiv1, indiv2;
				indiv1 = it.next();
				
				//indiv2 = it.hasNext() ? (JankenponIndividual) it.next() : null; We take a random individual instead
//				int index = rand.nextInt(nextGen.length);
//				if(indiv1 == nextGen[index]){ // it makes no sense in ELO to fight yourself
//					indiv2 = (JankenponIndividual) nextGen[rand.nextInt(nextGen.length)]; // never tell me the odds!
//				} else {
//					indiv2 = (JankenponIndividual) nextGen[index];
//				}
//					futures.add(cservice
//							.submit(new JankenponELOMatch(indiv1,indiv2)));
				
				
//				if(!this.ghostOfChristmasPast.isEmpty()){
//					for(int i= 0; i < this.ghostOfChristmasPast.size();i++){
//						if(this.ghostOfChristmasPast.get(i).fitnessResult.finalResult() > JankenponMatch.bossRatio){
//					futures.add(cservice
//						.submit(new JankenponMatch(indiv1,materializedGhosts.get(i),(int) Math.round(JankenponMatch.totalScore/scoremodification *this.ghostOfChristmasPast.get(i).fitnessResult.finalResult()))));
//					taskCount++;
//						}
//					}
//				}
				
				Iterator<JankenponIndividual> oiit =  worthyOpponents.iterator();
				while(oiit.hasNext()){
				indiv2 = (JankenponIndividual) oiit.next();
				
				if(indiv2 != indiv1 ){
					//System.out.println("Blu");
					futures.add(cservice
							.submit(new JankenponMatch(indiv1,indiv2)));
						taskCount++;
					}
				}
			}
			//System.err.println(Constants.popSize+" "+taskCount);
			for (int i = 0; i < taskCount; i++) {
				try {
					//System.out.println(this.maxRunTime);
					Future<Object> future = cservice.poll(this.maxRunTime, TimeUnit.SECONDS);
					if (future != null) {
						JankenponMatchResult result = (JankenponMatchResult) future
								.get();
						
						if(result.winner == null){
							System.err.println("Possible future interupt");
						}
						JankenponMatch.updatePlayers(result);
						//int index = Arrays.asList(nextGen).indexOf(result.winner);
						logger.logToMemory(numberOfMatches + ":Indiv " + result.winner.individualIndex
								+ "(Fitness "
								//+ result.winner.getFitnessString()
								+ ")"
								+ result.winner.printSimpleModel()
								+ " against "
								+ (result.loser==null?"nobody":result.loser.printSimpleModel()));
						numberOfMatches++;
					} else {
						//for (Future<Object> future1 : futures) {
						//	if (future1 != null) {
						//		future1.cancel(true);
						//	}
						//}
						//logger.logToMemory(i + ": Drop");
						//break;
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		
	}
	
	@Override
	public Individual crossOver(Individual indiv1, Individual indiv2)
			throws CloneNotSupportedException {
		Individual child = super.crossOver(indiv1, indiv2);
		String[] importantNodes = new String[]{ ConstantsJankenpon.JUDGESTRING, ConstantsJankenpon.JANSTRING, ConstantsJankenpon.KENSTRING, 
						ConstantsJankenpon.PONSTRING, ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.JANSTRING, 
						ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.KENSTRING, ConstantsJankenpon.contestantPrefix+ConstantsJankenpon.PONSTRING};
		for(String name : importantNodes){
		if(child.getNode(name) == null){
			if(rand.nextBoolean()){ // get it from parent1
				child.nodeGenes.add(indiv1.getNode(name).clone());
			} else {
				child.nodeGenes.add(indiv2.getNode(name).clone());
			}
		}
		}
		return child;
	}
	
	
	@Override
	protected void postSpeciation(ArrayList<Species> nextGenSpecies) {
		this.ghostOfChristmasPast = new ArrayList<JankenponIndividual>();
		for(Species sp: nextGenSpecies){
			this.ghostOfChristmasPast.add(((JankenponIndividual) sp.getBestIndividual()));
		}
		
	}
	
	@Override
	protected Individual clone(Individual indiv)
			throws CloneNotSupportedException {
		Individual clone = getNewIndividual(indiv.innovations, indiv.nodeNames,
				indiv.generationIndex, indiv.individualIndex);
		for (Node node : indiv.nodeGenes) {
			clone.nodeGenes.add(node.clone());
		}
		for (Connection connection : indiv.connectionGenes) {
			clone.addConnection(clone.getNode(connection.from.name),
					clone.getNode(connection.to.name), connection.enabled,
					connection.parameter);
		}
		clone.exo = indiv.exo;
		clone.pol = indiv.pol;
		clone.nick = indiv.nick;
		clone.fitnessResult = new JankenponResult();
		return clone;
	}


}
